﻿using System;
using System.Collections.Generic;
using System.Text;
using PhiStore.Policies;
using PhiStore.Util;

namespace PhiStore.Tests.BestCase
{
    class BestCase : ITest
    {
        private object mutex = new object();
        private bool completed = false;
        private List<Policy> policies;
        private List<string> internalLog = new List<string>();
        private List<PhiClient<double>> clients = new List<PhiClient<double>>();
        private bool success = true;
        private int replicationCount = 0;


        public BestCase(uint Replication, int clientCount, bool use2PC, bool compress, bool encrypt)
        {
            policies = Examples.AllCapabilityVariants(use2PC, false, 5 * 1000, Replication, true);
            replicationCount = (int) Replication;

            for (int i = 0; i < clientCount; i++)
            {
                PhiClient<double> client = new PhiClient<double>();
                client.Initialize(compress, encrypt ? "Hello": "");
                clients.Add(client);
            }
        }

        public string getResults()
        {
            string result = "";
            if (success)
            {
                result += "All tests completed successfully!" + Environment.NewLine;
            }

            foreach (string t in internalLog)
            {
                result += t + Environment.NewLine;
            }
            return result;

        }

        public bool IsComplete()
        {
            lock (mutex)
            {
                return completed;
            }
        }
        public void Run()
        {
            try
            {
                List<BroadcastObject> bcs = new List<BroadcastObject>();
                while (bcs.Count < replicationCount)
                {
                    BroadcastObject bc = ReceiveBroadcast.Receive(24281, 2);
                    if (!bcs.Contains(bc))
                    {
                        bcs.Add(bc);
                    }
                }

                try
                {
                    for (int i = 0; i < clients.Count; i++)
                    {
                        PhiClient<double> client = clients[i];
                        BroadcastObject bc = bcs[i % replicationCount];
                        
                        client.Connect(bc.IP, bc.Port);
                    }
                }
                catch
                {
                    internalLog.Add("Init and connect failed");
                    success &= false;
                }

                for (int x = 0; x < policies.Count && success; x++)
                {
                    Policy pol = policies[x];
                    internalLog.Add("Testing " + pol.ToString());

                    #region simple object test
                    string[] keys = new string[clients.Count];

                    foreach (PhiClient<double> client in clients)
                    {
                        success &= MethodTest<double>.Create(1.0, pol, client, true);
                    }
                    if (!success) internalLog.Add("Create failed");

                    for (int i = 0; i < clients.Count; i++)
                    {
                        keys[i] = x.ToString() + "keyclient" + (i + 1).ToString();
                        success &= MethodTest<double>.Create(keys[i], (i + 1), pol, clients[i], true);
                    }
                    if (!success) internalLog.Add("Create failed");

                    ArrayUtil.Randomize<string>(keys);

                    for (int i = 0; i < clients.Count; i++)
                    {
                        string key = keys[i];
                        double valueToRead = double.Parse(key.Replace(x.ToString() + "keyclient", ""));
                        success &= MethodTest<double>.Read(key, valueToRead, clients[i], true);
                    }

                    if (!success) internalLog.Add("Read failed");

                    for (int i = 0; i < clients.Count; i++)
                    {
                        string key = keys[i];
                        double valueToUpdate = 2 * double.Parse(key.Replace(x.ToString() + "keyclient", ""));
                        success &= MethodTest<double>.Update(key, valueToUpdate, clients[i], pol);
                        success &= MethodTest<double>.Read(key, valueToUpdate, clients[i], pol.MayModify);
                    }

                    if (!success) internalLog.Add("Update failed");

                    for (int i = 0; i < clients.Count; i++)
                    {
                        string key = keys[i];
                        double valueToRead = (pol.MayModify ? 2 : 1) * double.Parse(key.Replace(x.ToString() + "keyclient", ""));
                        success &= MethodTest<double>.Delete(key, clients[i], pol);
                        success &= MethodTest<double>.Read(key, valueToRead, clients[i], !pol.MayDelete);
                    }

                    if (!success) internalLog.Add("Delete failed");
                    #endregion

                    List<double> list = new List<double>();
                    list.Add(1.0);
                    list.Add(2.0);

                    foreach (PhiClient<double> client in clients)
                    {
                        success &= MethodTest<double>.ListCreate(list, pol, client, true);
                    }
                    if (!success) internalLog.Add("List Create failed");

                    for (int i = 0; i < clients.Count; i++)
                    {
                        List<double> newList = new List<double>();
                        foreach (double d in list)
                            newList.Add(d * (i + 1));

                        keys[i] = x.ToString() + "listkeyclient" + (i + 1).ToString();
                        success &= MethodTest<double>.ListCreate(keys[i], newList, pol, clients[i], true);
                    }
                    if (!success) internalLog.Add("List Create failed");

                    ArrayUtil.Randomize<string>(keys);

                    for (int i = 0; i < clients.Count; i++)
                    {
                        string key = keys[i];
                        double keyIndex = double.Parse(key.Replace(x.ToString() + "listkeyclient", ""));
                        List<double> valuesToRead = new List<double>();
                        foreach (double d in list)
                            valuesToRead.Add(d * keyIndex);

                        success &= MethodTest<double>.ListRead(key, 0, 2, valuesToRead, clients[i], true);
                        success &= MethodTest<double>.ListRead(key, 0, keyIndex, clients[i], true);
                    }
                    if (!success) internalLog.Add("List Read failed");


                    for (int i = 0; i < clients.Count; i++)
                    {
                        string key = keys[i];
                        double keyValue = double.Parse(key.Replace(x.ToString() + "listkeyclient", ""));
                        double valueToUpdate = 2 * keyValue;
                        success &= MethodTest<double>.ListUpdate(key, valueToUpdate, 0, clients[i], pol);
                        success &= MethodTest<double>.ListRead(key, 0, valueToUpdate, clients[i], pol.MayModify);

                        List<double> valuesToUpdate = new List<double>();
                        foreach (double d in list)
                            valuesToUpdate.Add(d * keyValue * 2);

                        success &= MethodTest<double>.ListUpdate(key, valuesToUpdate, 0, clients[i], pol);
                        success &= MethodTest<double>.ListRead(key, 0, 2, valuesToUpdate, clients[i], pol.MayModify);
                    }

                    if (!success) internalLog.Add("List Update failed");

                    for (int i = 0; i < clients.Count; i++)
                    {
                        string key = keys[i];
                        success &= MethodTest<double>.ListDelete(key, 0, clients[i], pol);
                        success &= MethodTest<double>.ListCount(key, 1, clients[i], pol.MayDelete);
                        
                        double valueToRead = (pol.MayModify ? 4 : 2) *  
                             double.Parse(key.Replace(x.ToString() + "listkeyclient", ""));
                        success &= MethodTest<double>.ListRead(key, 1, valueToRead, clients[i], !pol.MayDelete);
                    }

                    if (!success) internalLog.Add("List Delete failed");

                    for (int i = 0; i < clients.Count; i++)
                    {
                        string key = keys[i];
                        double valueToAppend = 3 * double.Parse(key.Replace(x.ToString() + "listkeyclient", ""));
                        long currentCount = clients[i].ListCount(key);
                        success &= MethodTest<double>.ListAppend(key, valueToAppend, clients[i], pol.MayAppend);
                        success &= MethodTest<double>.ListRead(key, currentCount, valueToAppend, clients[i], pol.MayAppend);
                        currentCount = clients[i].ListCount(key);

                        List<double> valuesToAppend = new List<double>();
                        valuesToAppend.Add(4 * double.Parse(key.Replace(x.ToString() + "listkeyclient", "")));
                        valuesToAppend.Add(5 * double.Parse(key.Replace(x.ToString() + "listkeyclient", "")));

                        success &= MethodTest<double>.ListAppend(key, valuesToAppend, clients[i], pol);
                        success &= MethodTest<double>.ListRead(key, currentCount, 2, valuesToAppend, clients[i], pol.MayAppend);

                        success &= MethodTest<double>.ListCount(key, currentCount+2, clients[i], pol.MayAppend);
                    }

                    if (!success) internalLog.Add("List Append failed");

                    for (int i = 0; i < clients.Count; i++)
                    {
                        string key = keys[i];
                        double valueToRead = (pol.MayModify && pol.MayDelete ? 4 :(pol.MayModify || pol.MayDelete ? 2: 1)) 
                            * double.Parse(key.Replace(x.ToString() + "listkeyclient", ""));
                        long currentCount = clients[i].ListCount(key);
                        success &= MethodTest<double>.ListPop(key, valueToRead, clients[i], pol);
                        success &= MethodTest<double>.ListCount(key, currentCount - 1, clients[i], pol.MayPop);
                    }

                    if (!success) internalLog.Add("List Pop failed");

                }

                try
                {
                    foreach (PhiClient<double> client in clients)
                    {
                        client.Disconnect();
                    }
                }
                catch
                {
                    internalLog.Add("Disconnect failed");
                    success &= false;
                }
            }
            catch (Exception e)
            {
                internalLog.Add("FAILURE:" + e.ToString());
                success &= false;
            }

            lock (mutex)
            {
                completed = true;
            }
        }

        ~BestCase()
        {
            foreach (PhiClient<double> client in clients)
            {
                client.Disconnect();
            }
        }
    }
}
